/**
 * 
 */
package edu.umd.clip.lm.model.decoding;

import java.text.*;
import java.util.*;
import java.util.concurrent.atomic.AtomicLongArray;

/**
 * @author Denis Filimonov <den@cs.umd.edu>
 *
 */
public class TrellisNodeCache {
	private final LinkedHashMap<TreeTrellisNode, TreeTrellisNode> cache;
	private final int maxSize;
	private final int MAX_NODES_AT_A_TIME = 3; 
	private final int statSize = 5;
	private final AtomicLongArray requests = new AtomicLongArray(statSize);
	private final AtomicLongArray hits = new AtomicLongArray(statSize);
	private final AtomicLongArray pruned = new AtomicLongArray(statSize);
	
	public TrellisNodeCache(int maxSize) {
		this.maxSize = maxSize;
		this.cache = new LinkedHashMap<TreeTrellisNode, TreeTrellisNode>(maxSize, 0.75f, true);
	}
	
	public void addNode(TreeTrellisNode node) {
		synchronized(cache) {
			cache.put(node, node);
			clean();
		}
	}
	
	public void touch(TreeTrellisNode node) {
		synchronized(cache) {
			if (cache.get(node) == null) {
				cache.put(node, node);
			}
			clean();
		}
	}
	private void clean() {
		if (cache.size() > maxSize) {
			LinkedList<String> prunedNodes = new LinkedList<String>();
			//StringBuilder sb = new StringBuilder();
			for(int i=0; i<MAX_NODES_AT_A_TIME; ++i) {
				Iterator<TreeTrellisNode> iter = cache.keySet().iterator();
				TreeTrellisNode node = iter.next();
				//sb.append("Trying to prune nodes: ");
				prunedNodes.clear();
				
				for(; node.isDeleted() || node.pruneNode(); node = node.getPreviousNode()) {
					//sb.append(node.toString());
					//sb.append(" ");
					cache.remove(node);
					prunedNodes.addFirst(node.toString());
				}
				cache.put(node, node);
				/*
				if (!prunedNodes.isEmpty()) {
					StringBuilder sb = new StringBuilder();
					sb.append("Pruned:");
					for(String s : prunedNodes) {
						sb.append(' ');
						sb.append(s);
					}
					System.err.println(sb.toString());
				}
				*/
				//sb.append('\n');
			}
			//System.err.print(sb.toString());
		}
	}
	
	protected void addHit(int time) {
		int idx = time < hits.length() ? time : statSize - 1;
		hits.incrementAndGet(idx);
	}
	
	protected void addRequest(int time) {
		int idx = time < requests.length() ? time : statSize - 1;
		requests.incrementAndGet(idx);		
	}
	
	protected void addPruned(int time) {
		int idx = time < pruned.length() ? time : statSize - 1;
		pruned.incrementAndGet(idx);		
	}
	
	public String getStats() {
		NumberFormat df = new DecimalFormat("00.00");
		StringBuilder sb = new StringBuilder();
		sb.append("hits/pruned/requests:");
		for(int time=1; time<statSize; ++time) {
			double hits = (double) this.hits.get(time) / this.requests.get(time) * 100;
			long pruned = this.pruned.get(time);
			long requests = this.requests.get(time);
			
			sb.append(' ');
			sb.append(df.format(hits));
			sb.append("%/");
			sb.append(pruned);
			sb.append('/');
			sb.append(requests);
			sb.append(" @");
			sb.append(time);
			if (time == statSize-1) {
				sb.append('+');
			} 
			sb.append(',');
		}
		return String.format("TrellisNodeCache stats: %s %d nodes in cache", sb.toString(), cache.size());
	}

}
